package com.sneaker.shower.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayOrderCloseResult;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.sneaker.shower.common.BizException;
import com.sneaker.shower.common.ThreadLocalCache;
import com.sneaker.shower.entity.domain.order.PayOrderDO;
import com.sneaker.shower.entity.domain.order.SubOrderDO;
import com.sneaker.shower.entity.domain.user.UserDO;
import com.sneaker.shower.entity.domain.user.UserLevelDO;
import com.sneaker.shower.entity.dto.UserDTO;
import com.sneaker.shower.entity.dto.WxPayParamDTO;
import com.sneaker.shower.entity.enums.SubOrderStatusEnum;
import com.sneaker.shower.mapper.PayOrderMapper;
import com.sneaker.shower.mapstruct.UserMapStruct;
import com.sneaker.shower.service.PayService;
import com.sneaker.shower.service.SubOrderService;
import com.sneaker.shower.service.UserLevelService;
import com.sneaker.shower.service.UserService;
import com.sneaker.shower.util.JacksonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;

import java.math.BigDecimal;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@Service
@Slf4j
public class PayServiceImpl extends ServiceImpl<PayOrderMapper, PayOrderDO> implements PayService {
    private static final Map<Long, WxPayParamDTO> MAP = new ConcurrentHashMap<>();
    private final AtomicInteger mCount = new AtomicInteger(0);
    private final ScheduledExecutorService se = Executors.newScheduledThreadPool(3, r -> new Thread(r, "Stick_msg_polling-" + mCount.incrementAndGet()));
    private UserService userService;
    private UserLevelService userLevelService;

    private SubOrderService subOrderService;
    @Value("${baseUrl}")
    private String baseUrl;
    private WxPayService wxService;

    @Autowired
    public PayServiceImpl(UserService userService,
                          UserLevelService userLevelService,
                          @Lazy SubOrderService subOrderService,
                          WxPayService wxPayService) {
        this.userService = userService;
        this.userLevelService = userLevelService;
        this.subOrderService = subOrderService;
        this.wxService = wxPayService;

    }

    @Override
    public WxPayMpOrderResult payInfo(WxPayParamDTO wxPayParamDTO) throws WxPayException {
        // 参数验证
        if (!checkOrderState(wxPayParamDTO)) {
            throw new BizException("支付请求发生变化，请刷新后重试");
        }

        PayOrderDO payOrderDO = this.getOne(new LambdaQueryWrapper<PayOrderDO>()
                .eq(PayOrderDO::getGuid, Long.parseLong(wxPayParamDTO.getPayGuid()))
                .eq(PayOrderDO::getVersion, wxPayParamDTO.getPayOrderVersion()));
        if (payOrderDO == null) {
            throw new BizException("支付请求发生变化，请刷新后重试");
        }

        String body;
        switch (payOrderDO.getType()) {
            case 0:
                body = "球鞋澡堂 - 订单支付";
                break;
            case 1:
                body = "球鞋澡堂 - 会员充值";
                break;
            case 2:
                body = "球鞋澡堂 - 线下支付";
                break;
            default:
                throw new BizException("支付失败，未知的支付类型");
        }
        WxPayUnifiedOrderRequest aa =
                WxPayUnifiedOrderRequest.newBuilder()
                        .attach(String.valueOf(payOrderDO.getGuid()))
                        .body(body)
                        .outTradeNo(String.valueOf(payOrderDO.getGuid()))
                        .notifyUrl(baseUrl + "/wx_pay/callBack")
                        .totalFee(Optional.ofNullable(payOrderDO.getActuallyPayFee()).orElse(BigDecimal.ZERO).multiply(BigDecimal.valueOf(100L)).intValue())
                        .spbillCreateIp("47.99.134.10")
                        .tradeType(WxPayConstants.TradeType.JSAPI)
                        .openid(ThreadLocalCache.get().getOpenId())
                        .build();
        if (wxService.getConfig().isUseSandboxEnv()) {
            String sandboxSignKey = wxService.getSandboxSignKey();
            wxService.getConfig().setMchKey(sandboxSignKey);
        }
        WxPayMpOrderResult order = wxService.createOrder(aa);
        log.info("预订单返回结果:{}", JacksonUtils.writeValueAsString(order));
        MAP.put(Long.parseLong(wxPayParamDTO.getPayGuid()), wxPayParamDTO);
        doPolling(payOrderDO);
        return order;
    }

    @Override
    public String callBack(String xmlData) {
        try {
            WxPayOrderNotifyResult wxPayOrderNotifyResult = wxService.parseOrderNotifyResult(xmlData);
            String outTradeNo = wxPayOrderNotifyResult.getOutTradeNo();
            PayOrderDO payOrderDO = this.getById(Long.parseLong(outTradeNo));
            if (payOrderDO != null) {
                if (Objects.equals(Byte.valueOf("1"), payOrderDO.getStatus())) {
                    return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
                }
                payOrderDO.setErrorMsg(wxPayOrderNotifyResult.getErrCode() + "," + wxPayOrderNotifyResult.getErrCodeDes());
                initThreadLocal(payOrderDO);
                if ("success".equalsIgnoreCase(wxPayOrderNotifyResult.getResultCode())) {
                    payOrderDO.setOrderNo(wxPayOrderNotifyResult.getTransactionId());
                    payOrderDO.setStatus(Byte.valueOf("1"));
                    WxPayParamDTO wxPayParamDTO = MAP.get(payOrderDO.getGuid());
                    if (wxPayParamDTO == null || !checkPayOrderType(payOrderDO, wxPayParamDTO)) {
                        return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
                    }
                    MAP.remove(payOrderDO.getGuid());
                } else {
                    payOrderDO.setStatus(Byte.valueOf("3"));
                    subOrderService.orderCallBack(payOrderDO, MAP.get(payOrderDO.getGuid()));
                }
                this.updateById(payOrderDO);
                return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
            } else {
                return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[order is not exist]]></return_msg></xml>";
            }
        } catch (WxPayException e) {
            log.error("消息回调失败 e:{}", e.getCustomErrorMsg());
            return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[check signature FAIL]]></return_msg></xml>";
        }
    }

    @Override
    public Boolean closeOrder(WxPayParamDTO wxPayParamDTO) {
        WxPayParamDTO payParamDTO = MAP.get(Long.parseLong(wxPayParamDTO.getPayGuid()));
        if (payParamDTO == null) {
            log.info("支付订单【{}】已完成", wxPayParamDTO.getPayGuid());
            return false;
        }
        try {
            WxPayOrderCloseResult wxPayOrderCloseResult = wxService.closeOrder(wxPayParamDTO.getPayGuid());
            log.info("关闭订单结果：{}", JacksonUtils.writeValueAsString(wxPayOrderCloseResult));
            MAP.remove(Long.parseLong(payParamDTO.getPayGuid()));
            return orderPayFailed(wxPayParamDTO);
        } catch (WxPayException e) {
            log.error("关闭支付订单失败：{}", e.getCustomErrorMsg());
            return false;
        }
    }

    @Override
    public Boolean isPaySuccess(String guid) {
        PayOrderDO payOrderDO = this.getById(guid);
        return StringUtils.equals(payOrderDO.getStatus().toString(), "1");
    }

//    public PayOrder updateFailOrder(Long guid, String errorMsg) {
//        PayOrder byId = this.getById(guid);
//        byId.setStatus(Byte.valueOf("2"));
//        byId.setErrorMsg(errorMsg);
//        updateById(byId);
//        return byId;
//    }

    private void doPolling(PayOrderDO payOrderDO) {
        if (payOrderDO == null) {
            log.error("支付轮询失败。参数为空");
            return;
        }
        String id = String.valueOf(payOrderDO.getGuid());
        AtomicInteger atomicInteger = new AtomicInteger(0);
        Map<String, Future> map = new ConcurrentHashMap<>(10);
        ScheduledFuture scheduledFuture;
        scheduledFuture = se.scheduleWithFixedDelay(() -> {
            try {
                int count = atomicInteger.incrementAndGet();
                if (count >= 60) {
                    log.info("当前订单：{}，轮询已达到次数上限，关闭订单", payOrderDO.getGuid());
                    wxService.closeOrder(String.valueOf(payOrderDO.getGuid()));
                    payOrderDO.setStatus(Byte.valueOf("2"));
                    payOrderDO.setErrorMsg("支付超时，关闭订单");
                    this.updateById(payOrderDO);
                    if (payOrderDO.getType() == 0) {
                        orderPayFailed(MAP.get(payOrderDO.getGuid()));
                    }
                    MAP.remove(payOrderDO.getGuid());
                    map.remove(id).cancel(true);
                    return;
                }
                if (MAP.get(payOrderDO.getGuid()) == null || !checkOrderState(MAP.get(payOrderDO.getGuid()))) {
                    log.info("当前订单：{}，已回调", payOrderDO.getGuid());
                    map.remove(id).cancel(true);
                    MAP.remove(payOrderDO.getGuid());
                    return;
                }
                WxPayOrderQueryResult wxPayOrderQueryResult = wxService.queryOrder(null, String.valueOf(payOrderDO.getGuid()));
                log.info("轮询查询结果：{}", JacksonUtils.writeValueAsString(wxPayOrderQueryResult));
                if (Objects.equals(WxPayConstants.ResultCode.SUCCESS, wxPayOrderQueryResult.getReturnCode()) && Objects.equals(WxPayConstants.WxpayTradeStatus.SUCCESS, wxPayOrderQueryResult.getResultCode())) {
                    if (Objects.equals(WxPayConstants.WxpayTradeStatus.USER_PAYING, wxPayOrderQueryResult.getTradeState()) ||
                            Objects.equals(WxPayConstants.WxpayTradeStatus.NOTPAY, wxPayOrderQueryResult.getTradeState())) {// 支付中 继续轮询 不做操作
                        log.info("当前订单【{}】支付中", payOrderDO.getGuid());
                        return;
                    }
                    if (Objects.equals(WxPayConstants.WxpayTradeStatus.SUCCESS, wxPayOrderQueryResult.getTradeState())) {
                        // 订单支付成功
                        payOrderDO.setStatus(Byte.valueOf("1"));
                        payOrderDO.setOrderNo(wxPayOrderQueryResult.getTransactionId());
                        payOrderDO.setVersion(MAP.get(payOrderDO.getGuid()).getPayOrderVersion());
                        boolean b = this.updateById(payOrderDO);
                        if (b) { // 支付订单状态修改成功了
                            initThreadLocal(payOrderDO);
                            checkPayOrderType(payOrderDO, MAP.get(payOrderDO.getGuid()));
                        }
                    } else {
                        // 支付失败
                        // 订单状态不是成功 全部理解为失败
                        payOrderDO.setStatus(Byte.valueOf("2"));
                        payOrderDO.setErrorMsg(wxPayOrderQueryResult.getTradeStateDesc());
                        this.updateById(payOrderDO);
                        if (payOrderDO.getType() == 0) {
                            orderPayFailed(MAP.get(payOrderDO.getGuid()));
                        }
                    }
                    MAP.remove(payOrderDO.getGuid());
                    map.remove(id).cancel(true);
                    log.info("订单【{}】轮询结束", payOrderDO.getGuid());
                } else {
                    log.error("订单【{}】轮询失败，returnMsg：【{}】, errCode:【{}】, errCodeDes: 【{}】",
                            payOrderDO.getGuid(),
                            wxPayOrderQueryResult.getReturnMsg(),
                            wxPayOrderQueryResult.getErrCode(),
                            wxPayOrderQueryResult.getErrCodeDes());
                    MAP.remove(payOrderDO.getGuid());
                    map.remove(id).cancel(true);
                }
            } catch (Exception e) {
                log.error("支付订单【{}】轮询异常：e{}", payOrderDO.getGuid(), e);
                e.printStackTrace();
                MAP.remove(payOrderDO.getGuid());
                map.remove(id).cancel(true);
            }
        }, 1, 1, TimeUnit.SECONDS);
        map.put(id, scheduledFuture);
    }

    private Boolean checkPayOrderType(PayOrderDO payOrderDO, WxPayParamDTO wxPayParamDTO) {
        try {
            switch (payOrderDO.getType()) {
                case 0:
                    subOrderService.orderCallBack(payOrderDO, wxPayParamDTO);
                    break;
                case 1:
                case 2:
                    userService.updateUserLevel(payOrderDO, wxPayParamDTO);
                    break;
                default:
                    log.info("unknown pay type[{}]", payOrderDO.getType());
                    break;
            }
            return true;
        } catch (BizException e) {
            log.info("订单【{}】状态修改失败，原因：{}", payOrderDO.getGuid(), e.getMessage());
            return false;
        }
    }

    private void initThreadLocal(PayOrderDO payOrderDO) {
        UserDO db = userService.getById(payOrderDO.getUserGuid());
        UserDTO userDTO = UserMapStruct.INSTANCE.do2DTO(db);
        userDTO.setOrganizationGuid(String.valueOf(payOrderDO.getOrganizationGuid()));
        userDTO.setNickName(new String(Base64Utils.decodeFromString(userDTO.getNickName())));
        userDTO.setRemarkName(new String(Base64Utils.decodeFromString(userDTO.getRemarkName())));
        ThreadLocalCache.set(userDTO);
    }

    private Boolean checkOrderState(WxPayParamDTO wxPayParamDTO) {
        if (!ObjectUtils.allNotNull(wxPayParamDTO.getPayGuid(),
                wxPayParamDTO.getPayOrderVersion(),
                wxPayParamDTO.getUserGuid(),
                wxPayParamDTO.getUserVersion())) {
            throw new BizException("订单参数有误，请重新下单");
        }

        if (wxPayParamDTO.getSubOrderGuid() != null && wxPayParamDTO.getSubOrderVersion() != null) {
            if (subOrderService.count(new LambdaQueryWrapper<SubOrderDO>()
                    .eq(SubOrderDO::getGuid, Long.parseLong(wxPayParamDTO.getSubOrderGuid()))
                    .eq(SubOrderDO::getVersion, wxPayParamDTO.getSubOrderVersion())) != 1) {
                return false;
            }
        }

        if (wxPayParamDTO.getUserLevelVersion() != null && wxPayParamDTO.getUserLevelGuid() != null) {
            if (userLevelService.count(new LambdaQueryWrapper<UserLevelDO>()
                    .eq(UserLevelDO::getGuid, wxPayParamDTO.getUserLevelGuid())
                    .eq(UserLevelDO::getVersion, wxPayParamDTO.getUserLevelVersion())) != 1) {
                return false;
            }
        }

        if (userService.count(new LambdaQueryWrapper<UserDO>()
                .eq(UserDO::getGuid, wxPayParamDTO.getUserGuid())
                .eq(UserDO::getVersion, wxPayParamDTO.getUserVersion())) != 1) {
            return false;
        }

        return this.count(new LambdaQueryWrapper<PayOrderDO>().eq(PayOrderDO::getGuid, wxPayParamDTO.getPayGuid())
                .eq(PayOrderDO::getVersion, wxPayParamDTO.getPayOrderVersion())) == 1;
    }

    private Boolean orderPayFailed(WxPayParamDTO wxPayParamDTO) {
        SubOrderDO subOrderDO = subOrderService.getById(wxPayParamDTO.getSubOrderGuid());
        subOrderDO.setStatus(SubOrderStatusEnum.PAY_FAILED.getCode());
        subOrderDO.setVersion(wxPayParamDTO.getSubOrderVersion());
        boolean b = subOrderService.updateById(subOrderDO);
        log.info("商品订单号【{}】, 支付订单号【{}】,支付失败。订单状态更新结果:【{}】",
                subOrderDO.getGuid(),
                wxPayParamDTO.getPayGuid(),
                b ? "成功" : "失败");
        return b;
    }

}
